
# 自动化特征选择
import mglearn.datasets
import numpy as np
import pandas as pd
from IPython.core.display_functions import display
from matplotlib import pyplot as plt
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.feature_selection import SelectPercentile, SelectFromModel, RFE
from sklearn.linear_model import LogisticRegression, LinearRegression, Ridge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, PolynomialFeatures, MinMaxScaler
from sklearn.svm import SVR
from sklearn.datasets import load_diabetes, load_breast_cancer


'''

虽然有多种方法可以创建新特征，但增加过多特征会使模型复杂化，增加过拟合的风险。
因此，在添加新特征或处理高维数据集时，最好减少特征数量，只保留最有用的特征，以获得更简单且泛化能力更好的模型。
判断特征重要性的三种基本策略包括：单变量统计（univariate statistics）、基于模型的选择（model-based selection）和迭代选择（iterative selection）。
这些方法都是监督方法，需要目标值来拟合模型，因此需要将数据划分为训练集和测试集，并仅在训练集上进行特征选择。

'''

# 单变量统计 （univariate statistics)
'''
单变量统计通过计算每个特征与目标值之间的关系来判断其统计显著性，并选择置信度最高的特征。在分类问题中，这种方法称为方差分析（ANOVA）。
由于单变量统计只考虑每个特征单独的影响，因此可能会忽略那些只有与其他特征结合才具有信息量的特征。
这种方法计算速度快，不需要构建模型，但完全独立于后续可能应用的模型。
'''

'''

在 scikit-learn 中，使用单变量特征选择可以通过以下步骤实现：
1.选择测试方法：
 对于分类问题，通常使用 f_classif，这是默认的测试方法。
 对于回归问题，通常使用 f_regression。
2.选择特征选择方法：
 SelectKBest：选择得分最高的 k 个特征。
 SelectPercentile：选择得分最高的一定百分比的特征。
3.应用特征选择：
 使用 SelectKBest 或 SelectPercentile 对数据集进行特征选择。例如，对于分类问题，可以使用 f_classif 和 SelectKBest 来选择得分最高的 k 个特征。

'''

cancer = load_breast_cancer()
rng = np.random.RandomState(42)
noise = rng.normal(size=(len(cancer.data),50))
X_w_noise = np.hstack([cancer.data,noise])

X_train,X_test,y_train,y_test = train_test_split(X_w_noise,cancer.target,random_state=0,test_size=.5)
select = SelectPercentile(percentile=50)
select.fit(X_train,y_train)
X_train_selected = select.transform(X_train)
print("X_train.shape: {}".format(X_train.shape))
print("X_train_selected.shape: {}".format(X_train_selected.shape))

# 如你所见，特征的数量从 80 减少到 40（原始特征数量的 50%）。我们可以用 get_support 方法来查看哪些特征被选中，
# 它会返回所选特征的布尔遮罩（mask）

mask = select.get_support()
# print(mask)
# 将遮罩可视化——黑色为True，白色为False
# plt.matshow(mask.reshape(1, -1), cmap='gray_r')
# plt.xlabel("Sample index")

'''
你可以从遮罩的可视化中看出，大多数所选择的特征都是原始特征，并且大多数噪声特征都已被删除。
但原始特征的还原并不完美。我们来比较 Logistic 回归在所有特征上的性能与仅使用所选特征的性能：
'''

# 对测试数据进行变换
X_test_selected = select.transform(X_test)
lr = LogisticRegression()
lr.fit(X_train, y_train)
print("Score with all features: {:.3f}".format(lr.score(X_test, y_test)))
lr.fit(X_train_selected, y_train)
print("Score with only selected features: {:.3f}".format(
 lr.score(X_test_selected, y_test)))
# Score with all features: 0.919
# Score with only selected features: 0.919

'''
在这个例子中，删除噪声特征可以提高性能，即使丢失了某些原始特征。这是一个非常简单的假想示例，在真实数据上的结果要更加复杂。
不过，如果特征量太大以至于无法构建模型，或者你怀疑许多特征完全没有信息量，那么单变量特征选择还是非常有用的。
'''

# 基于模型的特征选择（model-based selection）
'''

基于模型的特征选择是一种利用监督学习模型来评估特征重要性的方法，并保留最重要的特征。
这种方法的关键在于，用于特征选择的模型可以与最终建模的模型不同。以下是基于模型的特征选择的主要特点和实现方法：
特点
 模型无关性：用于特征选择的模型可以与最终模型不同。
 重要性度量：特征选择模型需要为每个特征提供重要性度量，以便对特征进行排序。
 考虑所有特征：与单变量选择不同，基于模型的选择同时考虑所有特征，能够捕捉特征之间的交互作用。
 稀疏性：某些模型（如带有 L1 惩罚的线性模型）自然倾向于稀疏解，这可以作为一种特征选择方法。
常见模型
 决策树和基于决策树的模型：这些模型提供了 feature_importances_ 属性，直接编码每个特征的重要性。
 线性模型：线性模型系数的绝对值可以用来表示特征的重要性。特别是带有 L1 惩罚的线性模型（如 Lasso），会学习稀疏系数，只使用特征的一个很小的子集。

要想使用基于模型的特征选择，我们需要使用 SelectFromModel 变换器：
'''

select = SelectFromModel(RandomForestRegressor(n_estimators=100, random_state=42),threshold="median")

'''
SelectFromModel 会选出重要性超过给定阈值的特征。为了和单变量特征选择对比，我们用特征重要性的中位数作为阈值，这样就能选出一半的特征。
我们用包含 100 棵树的随机森林分类器来计算特征重要性。随机森林是一个复杂的模型，比单变量测试强大得多。接下来，我们来拟合模型
'''
select.fit(X_train,y_train)
X_train_l1 = select.transform(X_train)
print("X_train.shape: {}".format(X_train.shape))
print("X_train_l1.shape: {}".format(X_train_l1.shape))

X_test_l1 = select.transform(X_test)
score = LogisticRegression().fit(X_train_l1, y_train).score(X_test_l1, y_test)
print("Test score: {:.3f}".format(score))
# Test score: 0.951

'''
利用更好的特征选择，性能也得到了提高。
'''

# 迭代特征选择 （iterative selection）
'''

单变量测试不使用模型，基于模型的选择使用单个模型选择特征，而迭代特征选择会构建一系列模型，通过逐个添加或删除特征来选择特征，计算成本更高。
递归特征消除（RFE）是一种特殊方法，从所有特征开始，逐步舍弃最不重要的特征，直到剩下预设数量的特征。
RFE需要模型能确定特征的重要性。我们使用之前的随机森林模型进行RFE，结果如下所示。
'''

select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40)
select.fit(X_train,y_train)
# mask = select.get_support()
# plt.matshow(mask.reshape(1,-1),cmap='gray_r')
# plt.xlabel("Sample index")

X_train_rfe = select.transform(X_train)
X_test_rfe = select.transform(X_test)
score = LogisticRegression().fit(X_train_rfe, y_train).score(X_test_rfe, y_test)
print("Test score: {:.3f}".format(score))
# Test score: 0.930

# 我们还可以利用在 RFE 内使用的模型来进行预测。这仅使用被选中的特征集：
print("Test score: {:.3f}".format(select.score(X_test, y_test)))

'''
RFE中使用的随机森林性能与在所选特征上训练的Logistic回归模型性能相同，表明正确选择特征后，线性模型表现可媲美随机森林。
自动化特征选择有助于确定特征输入，减少特征数量，加快预测速度，增强模型可解释性，虽不总能大幅提升性能，但仍是特征工程中的重要工具。
'''
